package com.cyf.common.core.io;

import cn.hutool.core.util.ArrayUtil;
import org.springframework.util.StreamUtils;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author chenyifan
 * @create 2024-11-05 10:40
 */
public class ZipUtil {

    /**
     * 指定文件或文件夹压缩成ZIP
     * @param src 源文件或文件夹
     * @param zipFile 生成的压缩文件路径
     * @param KeepDirStructure 是否保留原来的目录结构 true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     */
    public static void toZip(File src, File zipFile, boolean KeepDirStructure) throws IOException {
        FileUtil.createParentDir(zipFile);
        toZip(src, Files.newOutputStream(zipFile.toPath()), KeepDirStructure);
    }


    /**
     * 指定文件或文件夹压缩成ZIP
     * @param src 源文件
     * @param os 压缩文件输出流
     * @param KeepDirStructure 是否保留原来的目录结构 true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     */
    public static void toZip(File src, OutputStream os, boolean KeepDirStructure) {
        try (ZipOutputStream zos = new ZipOutputStream(os)) {
            compress(src, zos, src.getName(),  KeepDirStructure);
        } catch (Exception e) {
            throw new RuntimeException("压缩文件失败", e);
        }
    }

    /**
     * 文件列表压缩成ZIP
     * @param srcFiles 需要压缩的文件列表
     * @param zipFile 生成的压缩文件
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void toZip(List<File> srcFiles, File zipFile) throws IOException {
        FileUtil.createParentDir(zipFile);
        toZip(srcFiles, Files.newOutputStream(zipFile.toPath()));
    }

    /**
     * 文件列表压缩成ZIP
     * @param srcFiles 需要压缩的文件列表
     * @param os 压缩文件输出流
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void toZip(List<File> srcFiles, OutputStream os) {
        try (ZipOutputStream zos = new ZipOutputStream(os)) {
            for (File file : srcFiles) {
                zos.putNextEntry(new ZipEntry(file.getName()));
                try (InputStream fis = Files.newInputStream(file.toPath())) {
                    StreamUtils.copy(fis, zos);
                }
                zos.closeEntry();
            }
        } catch (Exception e) {
            throw new RuntimeException("压缩文件失败", e);
        }
    }

    /**
     * 递归压缩方法，空文件夹会忽略
     * @param source 源文件或文件夹
     * @param zos  zip输出流
     * @param entryPath 压缩包内路径
     * @param keepDirStructure  是否保留原来的目录结构,true:保留目录结构;
     *                          false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws Exception
     */
    private static void compress(File source, ZipOutputStream zos, String entryPath, boolean keepDirStructure) throws Exception{
        if(source.isFile()){
            // 向 zip 输出流中添加一个 ZipEntry，ZipEntry 构造器中 name 为 zip 实体的文件的名字
            zos.putNextEntry(new ZipEntry(entryPath));
            try (InputStream fis = Files.newInputStream(source.toPath())) {
                StreamUtils.copy(fis, zos);
            }
            zos.closeEntry();
        } else {
            File[] listFiles = source.listFiles();
            if (ArrayUtil.isEmpty(listFiles)) {
                return;
            }
            for (File file : listFiles) {
                if (keepDirStructure) {
                    compress(file, zos, Paths.get(source.getName(), file.getName()).toString(), keepDirStructure);
                } else {
                    // 如果不保留文件原来目录结构，在碰到文件名相同的文件时，会压缩失败。
                    compress(file, zos, file.getName(), keepDirStructure);
                }
            }
        }
    }
}
